/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts } from 'arkanalyzer/lib';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { AstMatcher, AstMatcherCallback, MatcherCallback, MatcherTypes } from '../../matcher/Matchers';
import { Rule } from '../../model/Rule';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ModuleTopLevelCodeCheck');
const topLevelStatementKinds = new Set([
    ts.SyntaxKind.VariableStatement,
    ts.SyntaxKind.ExpressionStatement,
    ts.SyntaxKind.IfStatement,
    ts.SyntaxKind.DoStatement,
    ts.SyntaxKind.WhileStatement,
    ts.SyntaxKind.ForStatement,
    ts.SyntaxKind.ForInStatement,
    ts.SyntaxKind.ForOfStatement,
    ts.SyntaxKind.SwitchStatement,
    ts.SyntaxKind.TryStatement,
    ts.SyntaxKind.Block
]);

export class ModuleTopLevelCodeCheck implements BaseChecker {
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    public metaData: BaseMetaData = {
        severity: 3,
        ruleDocPath: 'docs/module-top-level-code-check.md',
        description: 'May have side effects',
    };

    private nodeMatcher: AstMatcher = {
        matcherType: MatcherTypes.AST,
        match(node: ts.Node): boolean {
            return node.parent && ts.isSourceFile(node.parent) && topLevelStatementKinds.has(node.kind);
        }
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcher: AstMatcherCallback = {
            matcher: this.nodeMatcher,
            callback: this.check,
        };
        return [fileMatcher];
    }

    public check = (node: ts.Node, target: ArkFile): void => {
        if (target.getExportInfos().length < 1) {
            return;
        }
        if (this.isWarningMember(node)) {
            let sourceFile = target.getAST();
            if (sourceFile) {
                this.reportIssue(node, sourceFile, target.getFilePath());
            }
        }
    };

    private reportIssue(member: ts.Node, sourceFile: ts.SourceFile, filePath: string): void {
        // 获取AST位置信息
        const startAST = member.getStart(sourceFile);
        const { line: lineAST, character: startColAST } = ts.getLineAndCharacterOfPosition(sourceFile, startAST);

        // 计算实际位置
        const lineNum = lineAST + 1;
        const startColum = startColAST + 1;
        const endColum = startColAST + 1;
        const severity = this.rule.alert ?? this.metaData.severity;
        let defects = new Defects(lineNum, startColum, endColum, this.metaData.description, severity, this.rule.ruleId,
            filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
    }

    private isWarningMember(member: ts.Node): boolean {
        switch (member.kind) {
            case ts.SyntaxKind.IfStatement:
            case ts.SyntaxKind.DoStatement:
            case ts.SyntaxKind.WhileStatement:
            case ts.SyntaxKind.ForStatement:
            case ts.SyntaxKind.ForInStatement:
            case ts.SyntaxKind.ForOfStatement:
            case ts.SyntaxKind.SwitchStatement:
            case ts.SyntaxKind.TryStatement:
                return true;
            case ts.SyntaxKind.ExpressionStatement:
            case ts.SyntaxKind.VariableStatement:
            case ts.SyntaxKind.Block:
                return this.isSpecificExpression(member);
            default:
                return false;
        }
    }

    private isSpecificExpression(node: ts.Node): boolean {
        if (ts.isCallExpression(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
            return true;
        }
        return node.getChildren().some(c => this.isSpecificExpression(c));
    }
}